package glemipsc;
import glemipsc.implementations.Instruction;

import java.io.Serializable;
import java.util.*;

public class Type implements Serializable {
	public enum TypeFormat {
		VOID, INTEGER, FLOAT, CHAR, ARRAY, STRUCT, UNION, COMPLEX;
	};
	public enum StoreType {
		EXTERN, AUTO, REGISTER;
	};
	
	protected Type nestedType = null;
	protected TypeFormat typeFormat = TypeFormat.VOID;
	protected int length = 0;
	protected boolean signed = true;
	protected boolean longType = false;
	protected StoreType storeType = StoreType.AUTO;
	protected boolean staticElement = false;
	
	/**
	 * 
	 * @return the size of the value contained into the variable
	 */
	public int getValueLength() {
		return 0;
	}
	
	public int sizeOf() {
		return 1;
	}
	
	public Type getNestedType() {
		return nestedType;
	}
	public void setNestedType(Type type) {
		nestedType = type;
	}

	public TypeFormat getTypeFormat() {
		return typeFormat;
	}
	public void setTypeFormat(TypeFormat format) {
		typeFormat = format;
	}

	public void setSigned(boolean signed) {
		this.signed = signed;
	}
	public boolean isSigned() {
		return this.signed;
	}
	
	public void setLong(boolean longType) {
		this.longType = longType;
	}
	public boolean isLong() {
		return this.longType;
	}

	public void setStorageType(StoreType type) {
		storeType = type;
	}
	public StoreType getStorageType() {
		return storeType;
	}

	public void setStatic(boolean staticElement) {
		this.staticElement = staticElement;
	}
	public boolean isStatic() {
		return staticElement;
	}

	public void applyModifiers(List modifiers) {
		// SIGN_SPEC
		if (modifiers.contains("unsigned"))
			signed = false;
		if (modifiers.contains("signed"))
			signed = true;
		// LENGTH
		if (modifiers.contains("short"))
			longType = false;
		if (modifiers.contains("long"))
			longType = true;
		// STORAGE
		if (modifiers.contains("static"))
			staticElement = true;
		if (modifiers.contains("register"))
			storeType = StoreType.REGISTER;
		if (modifiers.contains("auto"))
			storeType = StoreType.AUTO;
		if (modifiers.contains("extern"))
			storeType = StoreType.EXTERN;
	}
	
	public int getLength() {
		return length;
	}
	
	public String getName() {
		return "void";
	}
	
	public boolean isCompatibleWith(Type type) {
		return type.typeFormat == this.typeFormat;
	}
	
	public boolean equals(Object o) {
		if (!this.getClass().equals(o.getClass()))
			return false;
		Type t = (Type) o;
		if (typeFormat != t.typeFormat)
			return false;
		if (signed != t.signed)
			return false;
		if (longType != t.longType)
			return false;
		return true;
	}

	public static Type createFromName(String name) {
		if (name.equals("int")) {
			return new TypeInteger();
		}
		else if (name.equals("long")) {
			Type type = new TypeInteger();
			type.setLong(true);
			return type;
		}
		else if (name.equals("short")) {
			Type type = new TypeInteger();
			type.setLong(false);
			return type;
		}
		else if (name.equals("float"))
			return new TypeFloat();
		else if (name.equals("double")) {
			Type type = new TypeFloat();
			type.setLong(true);
			return type;
		}
		else if (name.equals("char"))
			return new TypeChar();
		else if (name.equals("void"))
			return new Type();
		else
			return null;
	}
	
	public static Type getCommonType(Type t1, Type t2) {
		// Se uno dei due è un void
		if (t1.typeFormat == Type.TypeFormat.VOID)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.VOID)
			return t2;
		// Se uno dei due è un double
		if (t1.typeFormat == Type.TypeFormat.FLOAT && t1.longType)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.FLOAT && t2.longType)
			return t2;
		// Se uno dei due è un float
		if (t1.typeFormat == Type.TypeFormat.FLOAT && !t1.longType)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.FLOAT && !t2.longType)
			return t2;
		// Se uno è un unsigned long int
		if (t1.typeFormat == Type.TypeFormat.INTEGER && t1.longType && !t1.signed)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.INTEGER && t2.longType && !t2.signed)
			return t2;
		// long int con unsigned int
		// TODO: Type conversion between long int and unsigned int (long int >= unsigned int ? long int : unsigned long int)
		// Se uno dei due è long int
		if (t1.typeFormat == Type.TypeFormat.INTEGER && t1.longType && t1.signed)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.INTEGER && t2.longType && t2.signed)
			return t2;
		// Se uno dei due è unsigned int
		if (t1.typeFormat == Type.TypeFormat.INTEGER && !t1.signed)
			return t1;
		if (t2.typeFormat == Type.TypeFormat.INTEGER && !t2.signed)
			return t2;
		// Se sono compatibili prendo il più grande
		if (t1.isCompatibleWith(t2))
			return t1;
		if (t2.isCompatibleWith(t1))
			return t2;
		//return new TypeInteger();
		// TODO: IncompatibleTypeException?
		throw new RuntimeException();
	}
	
	public boolean computeInitialization(Address el, Object value, List<Instruction> instructions) {
		if (value != null)
			return false;
		return true;
	}
}
